%{
  #include <stdio.h>
  #include <stdlib.h>
  #include "quad.h" 
  #include "toassembler.h"

  extern FILE * yyin;

  void yyerror(char*);
  int yylex();
  void lex_free();

  struct symbol_table* table = NULL;
  struct list_quad* program = NULL;
  int nxt_quad = 0;
  int nb_temp = 0;
%}

%union {
  struct gen_quad* cond_type;
  struct list_quad* quad_list;
  struct array_quad* array;
  struct symbol_table* symbol;
  struct dim_struct* dim;
  char* string;
  int value;
}

%token ASSIGN WHILE DO DONE IF THEN ELSE ENDIF FOR TO STEP END MOINS_MOINS PLUS_PLUS
%token EQUAL INF INF_EQUAL SUP SUP_EQUAL DIFF TRUE FALSE OR AND NOT
%token ID STENCIL INT CONST MAIN RETURN PRINTD PRINTS STRING ABS
%token NUM

%type <string> ID STRING
%type <value> NUM M cst
%type <dim> list_tab
%type <array> list_tab_access
%type <symbol> declar ident stencil

%type <cond_type> statement statement_list condition def def_list
%type <quad_list> expression M2 increment

%right ASSIGN
%left OR
%left AND
%left NOT
%left '+' '-'
%left '*' '/'

%%

axiom:
  def_list INT MAIN '{' statement_list RETURN NUM ';' '}' {
      //On génère un label END pour complèter les dernier GOTO vides
      complete(&(program),nxt_quad);
      gen_code(&program,&nxt_quad,LABEL_END,NULL,NULL,NULL,-1);
      return 0;
    }
;

statement_list:
    statement_list M statement {
	complete(&($1->next),$2);
	$$ = $3;
    }
  | statement {
	$$ = $1;
    };

M: { $$ = nxt_quad; }
;

M2: { $$ = creer_list(gen_code(&program,&nxt_quad,OP_GOTO,NULL,NULL,NULL,-1)); }
;

ident: 
    ID {
	symbol_table* id = get_symbol(table, $1);
	if(id == NULL){ printf("Variable %s non définie\n",$1); exit(1);}
	$$ = id;
    }
  | list_tab_access {
	symbol_table* index = add_temp_symbol(&table,TYPE_ARRAY,&nb_temp,0);
	symbol_table* w_size = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,4);
	gen_code(&program,&nxt_quad,OP_ASSIGN_ADR,$1->array,NULL,index,-1);
	gen_code(&program,&nxt_quad,OP_MUL,$1->ptr,w_size,$1->ptr,-1);
	gen_code(&program,&nxt_quad,OP_ADD,index,$1->ptr,index,-1);
	$$ = index;
    } 
;

statement: 
    stencil ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	$$ = res;
    }
  | declar ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	$$ = res;
    }
  | ident ASSIGN expression ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	if($1->type == TYPE_CONSTANT){ printf("La constante %s ne peut être modifiée\n",$1->nom); exit(1);}
	if($1->type == TYPE_ARRAY) gen_code(&program,&nxt_quad,OP_ASSIGN_PTR,get_list_res($3),NULL,$1,-1);
	else gen_code(&program,&nxt_quad,OP_ASSIGN,get_list_res($3),NULL,$1,-1);
	$$ = res;
    }
  | declar ASSIGN expression ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	gen_code(&program,&nxt_quad,OP_ASSIGN,get_list_res($3),NULL,$1,-1);
	$$ = res;
    }
  | PRINTD '(' ident ')' ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	if($3->type == TYPE_STRING){ printf("print_d n'affiche que des entiers\n"); exit(1);} 
	gen_code(&program,&nxt_quad,OP_PRINT_DIGIT,$3,NULL,NULL,-1);
	$$ = res;
    }
  | PRINTS '(' STRING ')' ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* string = add_temp_string_symbol(&table,&nb_temp,$3); 
	gen_code(&program,&nxt_quad,OP_PRINT_STRING,string,NULL,NULL,-1);
	$$ = res;
    }
  | WHILE '(' M condition ')' '{' M statement_list '}' {
	gen_quad* res = malloc(sizeof(gen_quad));
	res->next = $4->faux;
	complete(&($4->vrai),$7);
	complete(&($8->next),$3);
	gen_code(&program,&nxt_quad,OP_GOTO,NULL,NULL,NULL,$3);
	$$ = res;
    }
  | IF '(' condition ')' '{' M statement_list '}' {
	gen_quad* res = malloc(sizeof(gen_quad));
	res->next =  concat(&($3->faux), &($7->next));
	complete(&($3->vrai),$6);
	list_quad* l = creer_list(gen_code(&program,&nxt_quad,OP_GOTO,NULL,NULL,NULL,-1));  
	concat(&(res->next),&l);
	$$ = res;
    }
  | IF '(' condition ')' '{' M statement_list '}' ELSE '{' M2 statement_list '}' {
	gen_quad* res = malloc(sizeof(gen_quad));
	complete(&($3->vrai),$6);
	complete(&($3->faux),$11->q->label+1);
	res->next =  concat(&($7->next), &($11));
	concat(&(res->next),&($12->next));
	list_quad* l = creer_list(gen_code(&program,&nxt_quad,OP_GOTO,NULL,NULL,NULL,-1));  
	concat(&(res->next),&l);
	$$ = res;
    }
  | FOR '(' ident ASSIGN expression {
	if($3->type == TYPE_ARRAY) gen_code(&program,&nxt_quad,OP_ASSIGN_PTR,get_list_res($5),NULL,$3,-1);
	else gen_code(&program,&nxt_quad,OP_ASSIGN,get_list_res($5),NULL,$3,-1);
    } ';' M condition {
	gen_quad* res = malloc(sizeof(gen_quad));
	concat(&(res->next),&($9->faux));
	$<cond_type>$ = res;
    } ';' increment ')' '{' statement_list '}' {
	$$ = $<cond_type>10;
	complete(&($15->next),nxt_quad);
	concat(&program,&($12));
	gen_code(&program,&nxt_quad,OP_GOTO,NULL,NULL,NULL,$8);
    }
  ;

declar: 
	INT ID {
	  symbol_table* id = get_symbol(table, $2);
	  if(id != NULL){ printf("Erreur : Variable %s déjà définie\n",$2); exit(0); }  
	  $$ = add_symbol(&table,$2,TYPE_VARIABLE,-1);
	}
      | INT ID list_tab {
	  symbol_table* id = get_symbol(table, $2);
	  if(id != NULL){ printf("Erreur : Variable %s déjà définie\n",$2); exit(0); }    
	  $$ = add_array_symbol(&table,$2,TYPE_ARRAY,$3);
	}
;

stencil:
	stencil NUM ',' {
		symbol_table* w_size = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,4);
		symbol_table* temp = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,$2);
		gen_code(&program,&nxt_quad,OP_ASSIGN_PTR,temp,NULL,$1,-1);
		gen_code(&program,&nxt_quad,OP_ADD,$1,w_size,$1,-1);
		$$ = $1;
        }
       | stencil NUM '}' {
		symbol_table* w_size = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,4);
		symbol_table* temp = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,$2);
		gen_code(&program,&nxt_quad,OP_ASSIGN_PTR,temp,NULL,$1,-1);
		gen_code(&program,&nxt_quad,OP_ADD,$1,w_size,$1,-1);
		$$ = $1;
         }
       | stencil '{' { $$ = $1; }
       | STENCIL ID '{' NUM ',' NUM '}' ASSIGN '{' {
	  symbol_table* id = get_symbol(table, $2);
	  if(id != NULL){ printf("Erreur : Variable %s déjà définie\n",$2); exit(0); }  
	  dim_struct* d = get_dim($4,$6);  
	  id = add_array_symbol(&table,$2,TYPE_ARRAY,d);
	  symbol_table* temp = add_temp_symbol(&table,TYPE_ARRAY,&nb_temp,0);
	  gen_code(&program,&nxt_quad,OP_ASSIGN_ADR,id,NULL,temp,-1);
	  $$ = temp;
	 }
;

def:
    CONST INT ID ASSIGN expression ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* id = get_symbol(table, $3);
	if(id != NULL){ printf("Erreur : Variable %s déjà définie\n",$3); exit(0); }
	id = add_symbol(&table,$3,TYPE_CONSTANT,-1);
	gen_code(&program,&nxt_quad,OP_ASSIGN,get_list_res($5),NULL,id,-1);
	$$ = res;
    }
  | declar ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	$$ = res;
    }
  | ID ASSIGN expression ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
   	symbol_table* id = get_symbol(table, $1);
	if(id == NULL){ printf("Variable %s non définie\n",$1); exit(1);}
	if(id->type == TYPE_CONSTANT){ printf("La constante %s ne peut être modifiée\n",id->nom); exit(1);}
	gen_code(&program,&nxt_quad,OP_ASSIGN,get_list_res($3),NULL,id,-1);
	$$ = res;
    }
  | declar ASSIGN expression ';' {
	gen_quad* res = malloc(sizeof(gen_quad));
	gen_code(&program,&nxt_quad,OP_ASSIGN,get_list_res($3),NULL,$1,-1);
	$$ = res;
    }	
;

def_list: 
    def_list def {
	$$=$2;
    }
  | {
	$$=malloc(sizeof(gen_quad));
    }
;

list_tab: 
	list_tab '[' cst ']' {
		dim_struct* d = malloc(sizeof(dim_struct));
		d->dim_size = $3;
		d->next = NULL;
		dim_struct* courrant = $1;
		while(courrant->next != NULL) courrant = courrant->next;
		courrant->next = d;
		$$ = $1;
	}
	| '[' cst ']' {
		dim_struct* d = malloc(sizeof(dim_struct));
		d->dim_size = $2;
		d->next == NULL;
		$$ = d;
	}
;

cst:
	ID {
		symbol_table* id = get_symbol(table, $1);
		if(id == NULL){ printf("Variable %s non définie\n",$1); exit(1);}
		if(id->type != TYPE_CONSTANT){ printf("La taille du tableau doit être connue à la compilation ! \n"); exit(1);}
		$<value>$ = id->valeur;
	}
      | NUM {
		$$=$<value>1;
	}
;

list_tab_access: 
	list_tab_access '[' expression ']' {
		int dim_sz = get_dim_size($1->array->dim,$1->ndim);
		symbol_table* dim_size = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,dim_sz);
		symbol_table* t = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,0);
		gen_code(&program,&nxt_quad,OP_MUL,$1->ptr,dim_size,t,-1);
		gen_code(&program,&nxt_quad,OP_ADD,t,get_list_res($3),t,-1);
		array_quad* aq = malloc(sizeof(array_quad));
		aq->array = $1->array;
		aq->ptr = t;
		aq->ndim = aq->ndim + 1;
		$$ = aq;
	}
	| ID '[' expression ']' {
		symbol_table* tableau = get_symbol(table, $1);
		if(tableau == NULL){ printf("Variable %s non définie\n",$1); exit(1);}
		symbol_table* temp = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,1);
		gen_code(&program,&nxt_quad,OP_ASSIGN,get_list_res($3),NULL,temp,-1);
		array_quad* aq = malloc(sizeof(array_quad));
		aq->array = tableau;
		aq->ptr = temp;
		aq->ndim = 1;
		$$ = aq;
	}
;

increment: 
     ident ASSIGN expression {
		list_quad* res = NULL;
		if($1->type == TYPE_CONSTANT){ printf("La constante %s ne peut être modifiée\n",$1->nom); exit(1);}
		if($1->type == TYPE_ARRAY) gen_code(&res,&nxt_quad,OP_ASSIGN_PTR,get_list_res($3),NULL,$1,-1);
		else gen_code(&res,&nxt_quad,OP_ASSIGN,get_list_res($3),NULL,$1,-1);
		$$=res;

      }
    | ident PLUS_PLUS {
		list_quad* res = NULL;
		if($1->type == TYPE_CONSTANT){ printf("La constante %s ne peut être modifiée\n",$1->nom); exit(1);}
		symbol_table* num = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,1);
		
		if($1->type == TYPE_ARRAY){
			symbol_table* temp = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,1);
			gen_code(&res,&nxt_quad,OP_ASSIGN_VAL,$1,NULL,temp,-1);
			gen_code(&res,&nxt_quad,OP_ADD,temp,num,temp,-1);
			gen_code(&res,&nxt_quad,OP_ASSIGN_PTR,temp,NULL,$1,-1);
		}
		else gen_code(&res,&nxt_quad,OP_ADD,$1,num,$1,-1);
		$$=res;
      }
    | ident MOINS_MOINS {
		list_quad* res = NULL;
		if($1->type == TYPE_CONSTANT){ printf("La constante %s ne peut être modifiée\n",$1->nom); exit(1);}
		symbol_table* num = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,1);
		
		if($1->type == TYPE_ARRAY){
			symbol_table* temp = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,1);
			gen_code(&res,&nxt_quad,OP_ASSIGN_VAL,$1,NULL,temp,-1);
			gen_code(&res,&nxt_quad,OP_SUB,temp,num,temp,-1);
			gen_code(&res,&nxt_quad,OP_ASSIGN_PTR,temp,NULL,$1,-1);
		}
		else gen_code(&res,&nxt_quad,OP_SUB,$1,num,$1,-1);
		$$=res;
      }
;

condition:
    expression EQUAL expression {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* expr1 = get_list_res($1);
	symbol_table* expr2 = get_list_res($3);
	if($1->q->operateur != NUL_QUAD) concat(&(res->faux),&($1));
	if($3->q->operateur != NUL_QUAD) concat(&(res->faux),&($3));
	res->faux = creer_list(gen_code(&program,&nxt_quad,OP_NEQUAL,expr1,expr2,NULL,-1));
	$$ = res;
    }
  | expression DIFF expression {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* expr1 = get_list_res($1);
	symbol_table* expr2 = get_list_res($3);
	if($1->q->operateur != NUL_QUAD) concat(&(res->faux),&($1));
	if($3->q->operateur != NUL_QUAD) concat(&(res->faux),&($3));
	res->faux = creer_list(gen_code(&program,&nxt_quad,OP_EQUAL,expr1,expr2,NULL,-1)); 
	$$ = res;
    }
  | expression INF expression {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* expr1 = get_list_res($1);
	symbol_table* expr2 = get_list_res($3);
	if($1->q->operateur != NUL_QUAD) concat(&(res->faux),&($1));
	if($3->q->operateur != NUL_QUAD) concat(&(res->faux),&($3));
	res->faux = creer_list(gen_code(&program,&nxt_quad,OP_SUP_EQUAL,expr1,expr2,NULL,-1)); 
	$$ = res;
    }
  | expression INF_EQUAL expression {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* expr1 = get_list_res($1);
	symbol_table* expr2 = get_list_res($3);
	if($1->q->operateur != NUL_QUAD) concat(&(res->faux),&($1));
	if($3->q->operateur != NUL_QUAD) concat(&(res->faux),&($3));
	res->faux = creer_list(gen_code(&program,&nxt_quad,OP_SUP,expr1,expr2,NULL,-1)); 
	$$ = res;
    }
  | expression SUP expression {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* expr1 = get_list_res($1);
	symbol_table* expr2 = get_list_res($3);
	if($1->q->operateur != NUL_QUAD) concat(&(res->faux),&($1));
	if($3->q->operateur != NUL_QUAD) concat(&(res->faux),&($3));
	res->faux = creer_list(gen_code(&program,&nxt_quad,OP_INF_EQUAL,expr1,expr2,NULL,-1)); 
	$$ = res;
    }
  | expression SUP_EQUAL expression {
	gen_quad* res = malloc(sizeof(gen_quad));
	symbol_table* expr1 = get_list_res($1);
	symbol_table* expr2 = get_list_res($3);
	if($1->q->operateur != NUL_QUAD) concat(&(res->faux),&($1));
	if($3->q->operateur != NUL_QUAD) concat(&(res->faux),&($3));
	res->faux = creer_list(gen_code(&program,&nxt_quad,OP_INF,expr1,expr2,NULL,-1));
	$$ = res;
    }
  | TRUE {
	gen_quad* res = malloc(sizeof(gen_quad));
	res->vrai = creer_list(gen_code(&program,&nxt_quad,OP_GOTO,NULL,NULL,NULL,-1));
	$$ = res;
    }
  | FALSE {
	gen_quad* res = malloc(sizeof(gen_quad));
	res->faux = creer_list(gen_code(&program,&nxt_quad,OP_GOTO,NULL,NULL,NULL,-1));
	$$ = res;
    }
  | condition OR M condition {
	gen_quad* res = malloc(sizeof(gen_quad));
	complete(&($1->faux),$3);
	res->vrai = concat(&($1->vrai), &($4->vrai));
	res->faux = $4->faux;
	$$ = res;
    }
  | condition AND M condition {
	gen_quad* res = malloc(sizeof(gen_quad));
	complete(&($1->vrai),$3);
	res->faux = concat(&($1->faux), &($4->faux));
	res->vrai = $4->vrai;
	$$ = res;
    }
  | NOT condition {
	gen_quad* res = malloc(sizeof(gen_quad));
	res->vrai = $2->faux;
	res->faux = $2->vrai;
	$$ = res;
    }
  | '(' condition ')' {
	gen_quad* res = malloc(sizeof(gen_quad));
	res->vrai = $2->vrai;
	res->faux = $2->faux;
	$$ = res;
    }
  ;

expression:
    expression '+' expression {
	list_quad* l = NULL;
	symbol_table* res = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,0);
	list_quad* new = creer_list(gen_code(&program,&nxt_quad,OP_ADD,get_list_res($1),get_list_res($3),res,-1));
	if($1->q->operateur != NUL_QUAD) concat(&l,&($1));
	if($3->q->operateur != NUL_QUAD) concat(&l,&($3));
	concat(&l,&new);
	$$ = l;
    }
  | expression '-' expression {
	list_quad* l = NULL;
	symbol_table* res = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,0);
	list_quad* new = creer_list(gen_code(&program,&nxt_quad,OP_SUB,get_list_res($1),get_list_res($3),res,-1));
	if($1->q->operateur != NUL_QUAD) concat(&l,&($1));
	if($3->q->operateur != NUL_QUAD) concat(&l,&($3));
	concat(&l,&new);
	$$ = l;
    }
  | expression '*' expression {
	list_quad* l = NULL;
	symbol_table* res = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,0);
	list_quad* new = creer_list(gen_code(&program,&nxt_quad,OP_MUL,get_list_res($1),get_list_res($3),res,-1));
	if($1->q->operateur != NUL_QUAD) concat(&l,&($1));
	if($3->q->operateur != NUL_QUAD) concat(&l,&($3));
	concat(&l,&new);
	$$ = l;
    }
  | expression '/' expression {
	list_quad* l = NULL;
	symbol_table* res = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,0);
	list_quad* new = creer_list(gen_code(&program,&nxt_quad,OP_DIV,get_list_res($1),get_list_res($3),res,-1));
	if($1->q->operateur != NUL_QUAD) concat(&l,&($1));
	if($3->q->operateur != NUL_QUAD) concat(&l,&($3));
	concat(&l,&new);
	$$ = l;
    }
  | ABS '(' expression ')' { 
	symbol_table* t = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,0);
	$$ = creer_list(gen_code(&program,&nxt_quad,OP_ABS,get_list_res($3),NULL,t,-1));
    }
  | ident {
	symbol_table* res;
	if($1->type == TYPE_ARRAY) {
		symbol_table* temp = add_temp_symbol(&table,TYPE_TEMP,&nb_temp,0);
		gen_code(&program,&nxt_quad,OP_ASSIGN_VAL,$1,NULL,temp,-1);
		res = temp;
	}
	else res = $1;
	quad* q = malloc(sizeof(quad));
	q->operateur = NUL_QUAD;
	q->res = res;
   	$$ = creer_list(q);
    }
  | NUM {
	symbol_table* num = add_temp_symbol(&table,TYPE_CONSTANT,&nb_temp,$1);
	quad* q = malloc(sizeof(quad));
	q->operateur = NUL_QUAD;
	q->res = num;
   	$$ = creer_list(q);
    };

%%

void yyerror (char *s) {
    fprintf(stderr, "[Yacc] error: %s\n", s);
}

int main(int argc, char** argv) {
  printf("Enter your code:\n");
  if(argc>1) yyin = fopen(argv[1],"r");
  FILE* fout = fopen("res.mips","w");
  yyparse();
  printf("-----------------\nSymbol table:\n");
  print_table(table);
  printf("-----------------\nQuad list:\n");
  print_quad_list(program);
  printf("-----------------\nMIPS ASM:\n");
  printassembler(fout,program,table); 

  // Be clean.
  lex_free();
  fclose(yyin);
  fclose(fout);
  //quad_free(quad_list);
  //symbol_free(symbol_table);
  return 0;
}
